package com.internetCafes.spms.web.certificate.service.impl;

import com.internetCafes.spms.common.AIRecognize.recognizer.impl.QRCodeRecognizer;
import com.internetCafes.spms.common.CommonConstants;
import com.internetCafes.spms.common.utils.ShiroUtils;
import com.internetCafes.spms.core.utils.FileUtils;
import com.internetCafes.spms.core.utils.IdWorker;
import com.internetCafes.spms.web.certificate.model.vo.airecognize.*;
import com.internetCafes.spms.web.certificate.entity.CertificateInfo;
import com.internetCafes.spms.web.certificate.entity.EmployeeInfo;
import com.internetCafes.spms.web.certificate.service.CertificateInfoService;
import com.internetCafes.spms.web.certificate.service.CertificateSubmitAIRecognizeService;
import com.internetCafes.spms.web.certificate.service.EmployeeInfoService;
import com.internetCafes.spms.web.certificate.vo.AIRecognizeInfo;
import com.internetCafes.spms.web.certificate.vo.CertificateInfoVo;
import com.internetCafes.spms.web.sys.model.UserInfo;
import com.internetCafes.spms.web.tenant.model.TenantDeptInfo;
import com.internetCafes.spms.web.tenant.service.TenantDeptInfoService;
import com.sms.common.util.hutool.core.util.ObjectUtil;
import lombok.extern.log4j.Log4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * 证书提交识别模块
 *
 * @author Zj
 */
@Log4j
@Service("certificateSubmitAIRecognizeService")
public class CertificateSubmitAIRecognizeServiceImpl implements CertificateSubmitAIRecognizeService {

    @Autowired
    private CertificateInfoService certificateInfoService;

    @Autowired
    private EmployeeInfoService employeeInfoService;

    @Autowired
    private TenantDeptInfoService tenantDeptInfoService;

    /**
     * 二维码识别器
     */
    @Autowired
    private QRCodeRecognizer qrCodeRecognizer;


    /**
     * 证书识别失败结果输出
     */
    private final Integer RECOGNIZE_ERROR_VO_TAG = 2;

    /**
     * 证书识别失败结果输出
     */
    private final Integer RECOGNIZE_SUCCESS_VO_TAG = 1;

    /**
     * 上传图片路径
     */
    @Value("${system.upload.pic}")
    private String picPath;

    @Value("${system.remote.url}")
    private String remoteUrl;


    /**
     * 证书批量保存
     *
     * @param certificateInfos 证书信息列表
     * @return 证书保存结果
     * @author Zj
     */
    @Override
    public RecognizeBatchSaveVO saveBatch(List<CertificateInfoVo> certificateInfos) {
        RecognizeBatchSaveVO result = new RecognizeBatchSaveVO();
        UserInfo userInfo = ShiroUtils.getUserEntity();

        // 获取部门列表
        Map<String, Object> params = new HashMap<>();
        params.put("tenantId", userInfo.getDeptId());
        List<TenantDeptInfo> depts = tenantDeptInfoService.query(params);
        Long deptId = 0L;
        if (CollectionUtils.isNotEmpty(depts)) {
            deptId = depts.get(0).getId();
        }

        // 证书信息验证
        for (CertificateInfoVo certificateTemp : certificateInfos) {
            // 获取在职员工持证人id
            if (certificateTemp.getCertificateType().equals(CommonConstants.CertificateType.SELF)) {
                params = new HashMap<>();
                params.put("tenantId", userInfo.getDeptId());
                params.put("idCard", certificateTemp.getIdCard());
                params.put("employeeFlag", CommonConstants.CommonStatus.ENABLE);
                List<EmployeeInfo> employees = employeeInfoService.query(params);
                if (CollectionUtils.isNotEmpty(employees)) {
                    certificateTemp.setEmployeeId(employees.get(0).getId());
                } else {
                    /**
                     * 如果没有在职员工人员信息，直接向数据库中插入一条人员数据 不进行报错录入
                     */
                    EmployeeInfo newEmployee = new EmployeeInfo();
                    BeanUtils.copyProperties(certificateTemp, newEmployee);
                    newEmployee.setDeptId(deptId);
                    newEmployee.setType(certificateTemp.getCertificateType());
                    newEmployee.setCreateTime(new Date());
                    newEmployee.setCreateBy(ShiroUtils.getUserId());
                    newEmployee.setTenantId(userInfo.getDeptId());
                    newEmployee.setType(CommonConstants.EmployeeInfoType.TENANT_Employee);
                    newEmployee.setStatus(CommonConstants.CommonStatus.ENABLE);
                    newEmployee.setEmployeeFlag(CommonConstants.CommonStatus.ENABLE);
                    employeeInfoService.save(newEmployee);
                    certificateTemp.setEmployeeId(newEmployee.getId());
                }
            }
            // 证书存在判断
            boolean existFlag = certificateInfoService.getExistFlag(certificateTemp.getCertificateNo(),
                    certificateTemp.getIdCard(), certificateTemp.getProfession());
            if (!existFlag) {
                result.setErrorAmount(result.getErrorAmount() + 1);
                result.getErrorList().add(result.new ErrorInfo(certificateTemp.getCertificateNo(), "证书已存在！"));
                continue;
            }

            certificateTemp.setCreateBy(ShiroUtils.getUserId());
            if (StringUtils.isBlank(certificateTemp.getPIdType())) {
                certificateTemp.setPIdType(certificateTemp.getConfType());
            }
            certificateTemp.setCreateTime(new Date());
            certificateInfoService.save(certificateTemp);
        }
        result.setSuccessAmount(certificateInfos.size() - result.getErrorAmount());
        return result;
    }

    /**
     * 批量证书文件识别
     *
     * @param files 文件列表
     * @return 识别结果封装
     * @author Zj
     */
    @Override
    public CertificateSubmitAIRecognizeBatchVO CertificateSubmitAIRecognizeBatch(MultipartFile[] files) {
        CertificateSubmitAIRecognizeBatchVO result = new CertificateSubmitAIRecognizeBatchVO();
        List<MultipartFile> filesList = Arrays.asList(files);

        // 1 - 轮询证书识别
        /*filesList.forEach(file -> {
            CertificateSubmitResultVO recognizeResult = certificateRecognize(file);
            if (RECOGNIZE_ERROR_VO_TAG.equals(recognizeResult.getResultTag())) {
                result.getErrorList().add(recognizeResult.getErrorInfo());
            }
            if (RECOGNIZE_SUCCESS_VO_TAG.equals(recognizeResult.getResultTag())) {
                RecognizeSuccessInfo successInfo = new RecognizeSuccessInfo();
                BeanUtils.copyProperties(recognizeResult.getSuccessInfo(), successInfo);
                successInfo.setFileUrl(successInfo.getRemoteUrl() + successInfo.getFileName());
                result.getSuccessList().add(successInfo);
            }
        });*/
        // 批量识别测试代码
        List<CertificateSubmitResultVO> recognizeResults = certificateRecognizeBatch(filesList);
        recognizeResults.forEach(recognizeResult -> {
            if (RECOGNIZE_ERROR_VO_TAG.equals(recognizeResult.getResultTag())) {
                result.getErrorList().add(recognizeResult.getErrorInfo());
            }
            if (RECOGNIZE_SUCCESS_VO_TAG.equals(recognizeResult.getResultTag())) {
                RecognizeSuccessInfo successInfo = new RecognizeSuccessInfo();
                BeanUtils.copyProperties(recognizeResult.getSuccessInfo(), successInfo);
                successInfo.setFileUrl(successInfo.getRemoteUrl() + successInfo.getFileName());
                if (result.getSuccessList().contains(successInfo)) {
                    result.getErrorList().add(new RecognizeErrorInfo(successInfo.getFileName(), "识别证书内容重复", successInfo.getFileUrl()));
                } else {
                    result.getSuccessList().add(successInfo);
                }
            }
        });
        result.setErrorAmount(result.getErrorList().size());
        int successRecognizeAmount = result.getSuccessList().size();
        int existAmount;
        // 成功证书去重

        // 2-证书已存在数据过滤 将重复的归类为失败的
        List<String> certificateNos = result.getSuccessList().stream()
                .map(RecognizeSuccessInfo::getCertificateNo)
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(certificateNos)) {
            // 2.1-获取已存在的证书
            List<CertificateInfo> certificateExistInfos = certificateInfoService.certificateInfosByNos(certificateNos);
            if (CollectionUtils.isNotEmpty(certificateExistInfos)) {
                List<String> existCertificateNos = certificateExistInfos.stream()
                        .map(CertificateInfo::getCertificateNo)
                        .collect(Collectors.toList());
                result.setSuccessList(result.getSuccessList().stream()
                        .filter(successTemp -> {
                            if (existCertificateNos.contains(successTemp.getCertificateNo())) {
                                result.getErrorList().add(new RecognizeErrorInfo(successTemp.getFileName(), "证书已存在", successTemp.getFileUrl()));
                            }
                            return !existCertificateNos.contains(successTemp.getCertificateNo());
                        }).collect(Collectors.toList()));
            }
        }

        int successNoExistAmount = result.getSuccessList().size();
        existAmount = successRecognizeAmount - successNoExistAmount;
        result.setSuccessAmount(successNoExistAmount);
        result.setExistAmount(existAmount);
        /**
         * 给成功list添加唯一编码
         */
        for (int i = 0; i < result.getSuccessList().size(); i++) {
            result.getSuccessList().get(i).setListId(i);
        }
        return result;
    }

    /**
     * 测试功能，通过多线程测试图片批量智能识别功能
     *
     * @param files 证书文件列表
     * @return 识别结果信息列表
     * @author Zj
     */
    private synchronized List<CertificateSubmitResultVO> certificateRecognizeBatch(List<MultipartFile> files) {
        //开启多线程
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        //结果集
        List<CertificateSubmitResultVO> results = new ArrayList<>();
        List<Future<CertificateSubmitResultVO>> futureList = new ArrayList<>();
        try {
            files.forEach(file -> handleFileTransform(results, futureList, file, executorService));
            //2.结果归集，用迭代器遍历futureList,高速轮询（模拟实现了并发），任务完成就移除
            while (futureList.size() > 0) {
                Iterator<Future<CertificateSubmitResultVO>> iterable = futureList.iterator();
                //遍历一遍
                while (iterable.hasNext()) {
                    Future<CertificateSubmitResultVO> future = iterable.next();
                    //如果任务完成取结果，否则判断下一个任务是否完成
                    if (future.isDone() && !future.isCancelled()) {
                        //获取结果
                        CertificateSubmitResultVO result = future.get();
                        results.add(result);
                        //任务完成移除任务
                        iterable.remove();
                    } else {
                        Thread.sleep(1);//避免CPU高速运转，这里休息1毫秒，CPU纳秒级别
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
        return results;
    }

    /**
     * 测试方法-将图片进行处理 进行保存或者对结果集添加失败数据
     * <p>
     * 批量并发保存
     * </p>
     *
     * @param results         结果集
     * @param futureList      future列表
     * @param file            文件
     * @param executorService 线程池
     * @author Zj
     */
    private void handleFileTransform(List<CertificateSubmitResultVO> results, List<Future<CertificateSubmitResultVO>> futureList, MultipartFile file, ExecutorService executorService) {
        // 1-文件为空
        if (file.isEmpty()) {
            results.add(CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo("", "上传文件不能为空", ""))
                    .build());
            return;
        }
        String fileSrcName = file.getOriginalFilename();
        if ((file.getSize() / 1024) > 1536) {
            results.add(CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo(fileSrcName, "文件大小不能大于1.5M", ""))
                    .build());
            return;
        }
        // 2-获取文件名称
        IdWorker id = new IdWorker(2);
        String fileName = id.nextId() + "";
        if (file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).toUpperCase().equals(FileUtils.PDF)) {
            try {
                fileName = FileUtils.pdfToJpg(file, picPath, fileName);
            } catch (Exception e) {
                results.add(CertificateSubmitResultVO.builder()
                        .resultTag(RECOGNIZE_ERROR_VO_TAG)
                        .errorInfo(new RecognizeErrorInfo(fileSrcName, "文件转存失败！", ""))
                        .build());
                return;
            }
        } else {
            try {
                fileName = FileUtils.transform(file, picPath, fileName, 2, "M", new String[]{FileUtils.JPG, FileUtils.PNG});
            } catch (Exception e) {
                results.add(CertificateSubmitResultVO.builder()
                        .resultTag(RECOGNIZE_ERROR_VO_TAG)
                        .errorInfo(new RecognizeErrorInfo(fileSrcName, "文件转存失败！", ""))
                        .build());
                return;
            }
        }
        String fileUrl = remoteUrl + File.separator + "pic" + File.separator + fileName;
        String finalFileName = fileName;
        futureList.add(executorService.submit(() -> certificateRecognizeSingle(finalFileName, fileSrcName, fileUrl)));
    }

    /**
     * <p>
     * 批量并发保存
     * </P>
     * 测试功能 单个证书识别数据封装
     *
     * @param fileName    文件名称
     * @param fileSrcName 源文件名称
     * @param fileUrl     文件url地址
     * @return 识别结果
     * @author Zj
     */
    private CertificateSubmitResultVO certificateRecognizeSingle(String fileName, String fileSrcName, String fileUrl) {
        // 3-证书识别
        AIRecognizeInfo aiRecognizeInfo = qrCodeRecognizer.recognize(picPath + File.separator + fileName);
        try {
            if (aiRecognizeInfo == null || ObjectUtil.isAllFieldNull(aiRecognizeInfo)) {
                return CertificateSubmitResultVO.builder()
                        .resultTag(RECOGNIZE_ERROR_VO_TAG)
                        .errorInfo(new RecognizeErrorInfo(fileSrcName, "上传图片有误或图片不清晰，请您确认后上传", fileUrl))
                        .build();
            }
        } catch (Exception e) {
            return CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo(fileSrcName, "证书识别异常", fileUrl))
                    .build();
        }

        // 姓名-身份证-证书编号-证书类型-证书专业-证书类别识别为空 也是错误
        if (StringUtils.isEmpty(aiRecognizeInfo.getName())
                || StringUtils.isEmpty(aiRecognizeInfo.getIdCard())
                || StringUtils.isEmpty(aiRecognizeInfo.getCertificateNo())
                || StringUtils.isEmpty(aiRecognizeInfo.getpIdType())
                || StringUtils.isEmpty(aiRecognizeInfo.getIdType())
                || StringUtils.isEmpty(aiRecognizeInfo.getProfession())
        ) {
            return CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo(fileSrcName, "证书识别错误", fileUrl))
                    .build();
        }

        // 4-根据专业类型获取证书大类以及证书小类
        if (aiRecognizeInfo.getProfession() != null
                && (aiRecognizeInfo.getpIdType() == null
                || aiRecognizeInfo.getIdType() == null)) {
            String[] idTypeAndPIdType = certificateInfoService.selectIdTypeByProfession(aiRecognizeInfo.getProfession());
            if (idTypeAndPIdType != null) {
                aiRecognizeInfo.setpIdType(idTypeAndPIdType[0]);
                aiRecognizeInfo.setIdType(idTypeAndPIdType[1]);
            }
        }

        // 5-获取同一个名称下
        Map<String, Object> params = new HashMap<>();
        params.put("name", aiRecognizeInfo.getName());
        List<EmployeeInfo> query = employeeInfoService.query(params);
        if (CollectionUtils.isNotEmpty(query)) {
            aiRecognizeInfo.setEmployeeId(query.get(0).getId());
        }
        aiRecognizeInfo.setFileName(fileSrcName);
        aiRecognizeInfo.setRemoteUrl(remoteUrl + File.separator + "pic" + File.separator);
        return CertificateSubmitResultVO.builder()
                .resultTag(RECOGNIZE_SUCCESS_VO_TAG)
                .successInfo(aiRecognizeInfo)
                .build();
    }

    /**
     * 图片智能识别封装 -
     * <p>
     * 无已存在判断 若需要，在返回参数中自行判断
     * </p>
     *
     * @param file 图片文件
     * @return CertificateSubmitResultVO 识别结果信息
     */
    private CertificateSubmitResultVO certificateRecognize(MultipartFile file) {
        // 1-文件为空
        if (file.isEmpty()) {
            return CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo("", "上传文件不能为空", ""))
                    .build();
        }
        String fileSrcName = file.getOriginalFilename();
        if (file.getSize() / 1024 > 1536) {
            return CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo(fileSrcName, "文件大小不能大于1.5M", ""))
                    .build();
        }
        // 2-获取文件名称
        IdWorker id = new IdWorker(2);
        String fileName = id.nextId() + "";
        if (file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).toUpperCase().equals(FileUtils.PDF)) {
            try {
                fileName = FileUtils.pdfToJpg(file, picPath, fileName);
            } catch (Exception e) {
                return CertificateSubmitResultVO.builder()
                        .resultTag(RECOGNIZE_ERROR_VO_TAG)
                        .errorInfo(new RecognizeErrorInfo(fileSrcName, "文件转存失败！", ""))
                        .build();
            }
        } else {
            try {
                fileName = FileUtils.transform(file, picPath, fileName, 2, "M", new String[]{FileUtils.JPG, FileUtils.PNG});
            } catch (Exception e) {
                return CertificateSubmitResultVO.builder()
                        .resultTag(RECOGNIZE_ERROR_VO_TAG)
                        .errorInfo(new RecognizeErrorInfo(fileSrcName, "文件转存失败！", ""))
                        .build();
            }
        }
        String fileUrl = remoteUrl + File.separator + "pic" + File.separator + fileName;
        // 3-证书识别
        AIRecognizeInfo aiRecognizeInfo = qrCodeRecognizer.recognize(picPath + File.separator + fileName);
        try {
            if (aiRecognizeInfo == null || ObjectUtil.isAllFieldNull(aiRecognizeInfo)) {
                return CertificateSubmitResultVO.builder()
                        .resultTag(RECOGNIZE_ERROR_VO_TAG)
                        .errorInfo(new RecognizeErrorInfo(fileSrcName, "上传图片有误或图片不清晰，请您确认后上传", fileUrl))
                        .build();
            }
        } catch (Exception e) {
            return CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo(fileSrcName, "证书识别异常", fileUrl))
                    .build();
        }

        // 姓名-身份证-证书编号-证书类型-证书专业-证书类别识别为空 也是错误
        if (StringUtils.isEmpty(aiRecognizeInfo.getName())
                || StringUtils.isEmpty(aiRecognizeInfo.getIdCard())
                || StringUtils.isEmpty(aiRecognizeInfo.getCertificateNo())
                || StringUtils.isEmpty(aiRecognizeInfo.getpIdType())
                || StringUtils.isEmpty(aiRecognizeInfo.getIdType())
                || StringUtils.isEmpty(aiRecognizeInfo.getProfession())
        ) {
            return CertificateSubmitResultVO.builder()
                    .resultTag(RECOGNIZE_ERROR_VO_TAG)
                    .errorInfo(new RecognizeErrorInfo(fileSrcName, "证书识别错误", fileUrl))
                    .build();
        }

        // 4-根据专业类型获取证书大类以及证书小类
        if (aiRecognizeInfo.getProfession() != null
                && (aiRecognizeInfo.getpIdType() == null
                || aiRecognizeInfo.getIdType() == null)) {
            String[] idTypeAndPIdType = certificateInfoService.selectIdTypeByProfession(aiRecognizeInfo.getProfession());
            if (idTypeAndPIdType != null) {
                aiRecognizeInfo.setpIdType(idTypeAndPIdType[0]);
                aiRecognizeInfo.setIdType(idTypeAndPIdType[1]);
            }
        }

        // 5-获取同一个名称下
        Map<String, Object> params = new HashMap<>();
        params.put("name", aiRecognizeInfo.getName());
        List<EmployeeInfo> query = employeeInfoService.query(params);
        if (CollectionUtils.isNotEmpty(query)) {
            aiRecognizeInfo.setEmployeeId(query.get(0).getId());
        }
        aiRecognizeInfo.setFileName(fileSrcName);
        aiRecognizeInfo.setRemoteUrl(remoteUrl + File.separator + "pic" + File.separator);
        return CertificateSubmitResultVO.builder()
                .resultTag(RECOGNIZE_SUCCESS_VO_TAG)
                .successInfo(aiRecognizeInfo)
                .build();
    }
}
